`ifndef ETH_UDP_SVH
`define ETH_UDP_SVH
class eth_udp;
    virtual mac_bfm bfm;
    /*
     * 静态属性
     */
    udp_pck_s   udp_pck;
    function new(virtual mac_bfm b);
        bfm = b;
    endfunction

    // function void udp_config (bit [31:00] command, byte value[]);
    function void udp_config (byte value[]);
        byte stream[];
        bit [31:0] checksum = '0;
        //bit [31:0] old_crc = 32'hff_ff_ff_ff;
        bit [31:0] old_crc = 32'h00_00_00_00;

        this.udp_pck.head.preamble = 64'h55_55_55_55_55_55_55_d5;
        this.udp_pck.head.dst_mac_addr = DEV_MAC_ADDR;
        this.udp_pck.head.src_mac_addr = PC_MAC_ADDR;
        this.udp_pck.head.ether_type = IPV4;

        this.udp_pck.ip.version = 4'd4;
        this.udp_pck.ip.ihl = 4'd5;
        this.udp_pck.ip.tos = 8'h00;
        this.udp_pck.ip.tol_length = 16'd20+8+$size(value);  // IP首部20字节 + UDP（或者其他子协议）全部数据(8字节首部+数据）
        this.udp_pck.ip.id = 16'h0000;
        this.udp_pck.ip.flags = 3'b010;
        this.udp_pck.ip.frag_offset = 13'd0;
        this.udp_pck.ip.ttl = 8'h40;
        this.udp_pck.ip.protocal = 8'h11;
        this.udp_pck.ip.checksum = 16'hb5ff;
        this.udp_pck.ip.src_ip_addr = PC_IP_ADDR;
        this.udp_pck.ip.dst_ip_addr = DEV_IP_ADDR;
        $display("%d, %d, %d, %d", DEV_IP_ADDR[31:24], DEV_IP_ADDR[23:16], DEV_IP_ADDR[15:8], DEV_IP_ADDR[7:0]);

        this.udp_pck.udp.src_port = PC_UDP_PORT;
        this.udp_pck.udp.dst_port = DEV_UDP_PORT;
        this.udp_pck.udp.length   = $size(value);
        this.udp_pck.udp.checksum = 16'hdddd;

        // 传进来的数据主要用于计算checksum
        stream = {>>{
            this.udp_pck.head, 
            this.udp_pck.ip,
            this.udp_pck.udp,
            value,
            this.udp_pck.checksum
            }} ;

        // 去掉前导符和最后的4个校验字节
        for(int i = 8; i < $size(stream)-4; i = i + 1 )
        begin
            //$display("crc first value %x", old_crc);
            old_crc = calc_crc(stream[i], old_crc);
            // $display("crc input is %x; crc result is %x", stream[i], old_crc);
        end
        this.udp_pck.checksum = {old_crc[7:0], old_crc[15:8], old_crc[23:16], old_crc[31:24]};
        // this.udp_pck.checksum = {old_crc[31:24], old_crc[23:16], old_crc[15:8],  old_crc[7:0]};
        // $display("udp_pck.checksum is %x", this.udp_pck.checksum);
    endfunction: udp_config

    function bit[32:0] calc_crc(bit [7:0] data, bit [31:0] init);
        reg [31:0] crc;
        reg        crc_feedback;
        integer    I;
        begin
            crc = ~ init;
            for (I = 0; I < 8; I = I + 1)
            begin
                crc_feedback = crc[0] ^ data[I];

                crc[0]       = crc[1];
                crc[1]       = crc[2];
                crc[2]       = crc[3];
                crc[3]       = crc[4];
                crc[4]       = crc[5];
                crc[5]       = crc[6]  ^ crc_feedback;
                crc[6]       = crc[7];
                crc[7]       = crc[8];
                crc[8]       = crc[9]  ^ crc_feedback;
                crc[9]       = crc[10] ^ crc_feedback;
                crc[10]      = crc[11];
                crc[11]      = crc[12];
                crc[12]      = crc[13];
                crc[13]      = crc[14];
                crc[14]      = crc[15];
                crc[15]      = crc[16] ^ crc_feedback;
                crc[16]      = crc[17];
                crc[17]      = crc[18];
                crc[18]      = crc[19];
                crc[19]      = crc[20] ^ crc_feedback;
                crc[20]      = crc[21] ^ crc_feedback;
                crc[21]      = crc[22] ^ crc_feedback;
                crc[22]      = crc[23];
                crc[23]      = crc[24] ^ crc_feedback;
                crc[24]      = crc[25] ^ crc_feedback;
                crc[25]      = crc[26];
                crc[26]      = crc[27] ^ crc_feedback;
                crc[27]      = crc[28] ^ crc_feedback;
                crc[28]      = crc[29];
                crc[29]      = crc[30] ^ crc_feedback;
                crc[30]      = crc[31] ^ crc_feedback;
                crc[31]      =           crc_feedback;
            end
            return ~ crc;
        end
    endfunction
    // task send_udp(bit [31:00] command, byte value[]);
    task send_udp(byte data[]);
        byte stream[];
        udp_config(data);
        stream = {>>{
            this.udp_pck.head, 
            this.udp_pck.ip,
            this.udp_pck.udp,
            data,
            this.udp_pck.checksum
            }} ;

        bfm.gmii_rx_dv = 0;
        bfm.gmii_rx_er = 0;
        bfm.gmii_rxd = 0;
        for(int i = 0; i < $size(stream); i = i + 1 )
        begin
            //@ (negedge bfm.gmii_rx_clk);
            @ bfm.ether_rx_cb;
            bfm.gmii_rx_dv = 1;
            bfm.gmii_rxd = stream[i];
            // $display("stream %d is %x", i, stream[i]);
        end
        //@ (negedge bfm.gmii_rx_clk);
        @ bfm.ether_rx_cb;
        bfm.gmii_rx_dv = 0;
    endtask:send_udp
endclass

`endif
